package org.openlaszlo.eu.oflm.service;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.openlaszlo.eu.dto.ErrorMessageDTO;
import org.openlaszlo.eu.io.FileManager;
import org.openlaszlo.eu.io.FileManagerException;
import org.openlaszlo.eu.io.dto.FileDescriptorDTO;

/**
 * Utility servlet handling REST style requests, performing tasks on files. This
 * servlet could be used with any front-end, as long as required parameters and
 * URL style is prerequisites are met.
 * 
 * Currently the path required on the URL is hardcoded, this can be made
 * configurable in the future. URL's the servlet requires: <br>
 * contextroot/fs/read?file=... <br>
 * where file is the file name including path that must be read.<br>
 * contextroot/fs/listdir?dir=.[&level=]<br>
 * where dir is the directory to list and level is the depth level until the
 * request should go. The level is by default equals to -1 which means make a
 * full recursive list.<br>
 * contextroot/fs/upload?dir=.<br>
 * where dir is the location to upload the file.<br>
 * contextroot/fs/delete?file=fileName<br>
 * where fileName is the file or directory to delete. <b>CAUTION</b> it could
 * <b>delete</b> your whole disk.<br>
 * contextroot/fs/move?srcfile=sourceFileName&destfile=destinationFileName<br>
 * where sourcefileName the file to move and the destination is the new name of the file.<br>
 * @author Raoul Zon (XB Software Solutions BV, The Netherlands,
 *         http://www.xb.nl)
 * @author Dominique Vernier (http://www.vernier.be)
 */

public class FileHandlingServlet extends HttpServlet {

	private static final long serialVersionUID = -7606122669358524057L;
	private static String DELETE_FILE = "delete";
	private static String READ_FILE = "read";
	private static String MOVE_FILE = "move";
	private static String LIST_DIRECTORY = "listdir";
	private static String UPLOAD_FILE = "upload";
	private static String MKDIR_FILE = "mkdir";

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		// Retrieve the url as string
		String requestUrl = request.getRequestURI().toString();
		// just check part of URL for now, make this more fancy/configurable in
		// the future
		String[] requestArray = requestUrl.split("/");
		String requestMethod = requestArray[requestArray.length - 1];

		// is the request method correctly set, if not send error
		if (requestMethod == null || "".equals(requestMethod)) {
			response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
			return;
		} else {
			if (UPLOAD_FILE.equals(requestMethod)) {
				doFileUpload(request, response);
			} else
			// If listdir then execute the doListDir with the correct parameters
			if (LIST_DIRECTORY.equals(requestMethod)) {
				String dirToRead = request.getParameter("dir");
				if (dirToRead == null || "".equals(dirToRead)) {
					response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
					return;
				}
				String level = request.getParameter("level");
				doListDir(dirToRead, level, response);
			} else if (MOVE_FILE.equals(requestMethod)) {
				// execute move
				String srcFileName = request.getParameter("srcfile");
				String destFileName = request.getParameter("destfile");
				if (srcFileName == null || "".equals(srcFileName)
						|| destFileName == null || "".equals(destFileName)) {
					response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
					return;
				}
				doFileMove(srcFileName, destFileName, response);
			} else if (MKDIR_FILE.equals(requestMethod)) {
				// execute mkdir
				String fileName = request.getParameter("dir");
				if (fileName == null || "".equals(fileName)) {
					response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
					return;
				}
				doMakeDir(fileName, response);
			} else  {
				// otherwise retrieve the file parameter
				String fileName = request.getParameter("file");
				if (fileName == null || "".equals(fileName)) {
					response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
					return;
				}
				// execute delete.
				if (DELETE_FILE.equals(requestMethod)) {
					doFileDelete(fileName, response);
				}
				// execute read
				else if (READ_FILE.equals(requestMethod)) {
					doFileRead(fileName, response);
				}
				// unknown method
				else {
					response.sendError(HttpServletResponse.SC_BAD_REQUEST);
					return;
				}
			}
		}
	}

	/**
	 * 
	 * This method retreives the file content from the request and use a dir
	 * parameter to locate the directory destination. The file name is embeded
	 * in the multipart of the request.
	 * 
	 * @param request
	 *            The multipart request containing the file content and its name
	 * @param response
	 *            The xml response in case of error.
	 * @throws IOException
	 */
	private void doFileUpload(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		if (!isMultipart) {
			response.sendError(HttpServletResponse.SC_BAD_REQUEST);
			return;
		}
		// Check that we have a file upload request
		String responseTxt = null;
		String dirToLoad = request.getParameter("dir");
		// Create a factory for disk-based file items
		FileItemFactory factory = new DiskFileItemFactory();

		// Create a new file upload handler
		ServletFileUpload upload = new ServletFileUpload(factory);

		// Parse the request
		List<FileItem> items;
		try {
			items = upload.parseRequest(request);
			FileManager fm = new FileManager();
			// Process the uploaded items
			Iterator<FileItem> iter = items.iterator();
			while (iter.hasNext()) {
				FileItem item = (FileItem) iter.next();
				if (!item.isFormField()) {
					// Save the item at the dirToLoad location
					String fileName = item.getName();
					if (dirToLoad != null && !"".equals(dirToLoad))
						fileName = dirToLoad+"/"+fileName;
					fm.update(fileName, item.get());
				}
			}
		} catch (FileUploadException fue) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fue);
			responseTxt = errorMessageDTO.toXML();
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					responseTxt);
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			responseTxt = errorMessageDTO.toXML();
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					responseTxt);
		}

	}

	/**
	 * 
	 * @param dirToRead
	 *            The directory to list
	 * @param level
	 *            The level of list, if -1 then full recursive directory list
	 *            will be done.
	 * @param response
	 * @throws IOException
	 *             If a problem occurs with the OutputSteam.
	 */
	private void doListDir(String dirToRead, String level,
			HttpServletResponse response) throws IOException {
		FileManager fm = new FileManager();
		FileDescriptorDTO brdto = null;
		String responseTxt = null;
		OutputStream stream = response.getOutputStream();
		try {
			if (level == null || "".equals(level))
				brdto = fm.browse(dirToRead);
			else
				brdto = fm.browse(dirToRead, Integer.valueOf(level).intValue());
			if (brdto != null) {
				responseTxt = brdto.toXML();
				stream.write(responseTxt.getBytes());
			}
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			responseTxt = errorMessageDTO.toXML();
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					responseTxt);
		} finally {
			stream.flush();
			stream.close();
		}
	}

	/**
	 * 
	 * This method deletes a directory and its content or a file
	 * 
	 * @param file
	 *            The file or directory to delete
	 * @param response
	 *            The HTTP response
	 * @throws IOException
	 */
	private void doFileDelete(String file, HttpServletResponse response)
			throws IOException {
		FileManager fm = new FileManager();
		try {
			fm.delete(file);
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					errorMessageDTO.toXML());
		}
	}

	/**
	 * 
	 * This method creates a directory
	 * 
	 * @param file
	 *            The directory to create
	 * @param response
	 *            The HTTP response
	 * @throws IOException
	 */
	private void doMakeDir(String file, HttpServletResponse response)
			throws IOException {
		FileManager fm = new FileManager();
		try {
			fm.mkdir(file);
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					errorMessageDTO.toXML());
		}
	}

	/**
	 * Reads the given file and streams it's content back to the requesting
	 * party
	 * 
	 * @param file
	 *            The name of the file to stream
	 * @param response
	 *            The HttpServletResponse
	 * @throws IOException
	 *             If a problem occurs with the OutputStream
	 */
	private void doFileRead(String file, HttpServletResponse response)
			throws IOException {
		FileManager fm = new FileManager();
		OutputStream stream = response.getOutputStream();
		try {
			byte[] array = fm.read(file);
			stream.write(array);
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					errorMessageDTO.toXML());
		} finally {
			stream.flush();
			stream.close();
		}
	}

	/**
	 * This method will move one file to another location.
	 * @param srcFileName The source file
	 * @param dstFileName The destination file.
	 * @param response The HttpServletResponse
	 * @throws IOException
	 */
	private void doFileMove(String srcFileName, String dstFileName,
			HttpServletResponse response) throws IOException {
		FileManager fm = new FileManager();
		try {
			fm.move(srcFileName, dstFileName);
		} catch (FileManagerException fme) {
			ErrorMessageDTO errorMessageDTO = new ErrorMessageDTO(fme);
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					errorMessageDTO.toXML());
		}
	}
}
